package com.chapter.sample.answer.exporter;

import com.chapter.sample.answer.exporter.annotations.RedisCacheAnnotation;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

@Component
@Aspect
@Slf4j
public class RedisCacheAspect {
    @Resource
    private RedisTemplate redisTemplate;

    //配置织入点
    @Pointcut("@annotation(com.chapter.sample.answer.exporter.annotations.RedisCacheAnnotation)")
    public void cachePointCut() {
    }


    @Around("cachePointCut()")
    public Object doCache(ProceedingJoinPoint joinPoint) {

        Object result = null;
        try {
            //1、获取重载后的方法名
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Parameter[] parameters = method.getParameters();
            System.out.println(parameters);

            //2、确定方法名后，获得该方法上面配置的注解标签RedisCacheAnnotation
            RedisCacheAnnotation redisCacheAnnotation = method.getAnnotation(RedisCacheAnnotation.class);

            //3、拿到了RedisCacheAnnotation这个注解标签，获得该注解上面配置的参数进行封装和调用
            String keyPrefix = redisCacheAnnotation.keyPrefix();
            String matchValveSpringEL = redisCacheAnnotation.matchValve();

            //4、SpringEl 解析器
            ExpressionParser parser = new SpelExpressionParser();
            Expression expression = parser.parseExpression(matchValveSpringEL);//#id
            EvaluationContext context = new StandardEvaluationContext();

            //5、获得方法里的形参个数
            Object[] args = joinPoint.getArgs();
            DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
            String[] parameterNames = discoverer.getParameterNames(method);
            for (int i = 0; i < parameterNames.length; i++) {
                log.info("获得方法里参数名：「{}」和值名：「{}」", parameterNames[i], args[i]);
                context.setVariable(parameterNames[i], args[i].toString());
            }

            //6、通过上述，拼接redis的最终key形式
            String key = keyPrefix + ":" + expression.getValue(context).toString();
            log.info("拼接redis的最终key形式：「{}」", key);

            //7、先去redis里面查询看有没有
            result = redisTemplate.opsForValue().get(key);
            if (result != null) {
                log.info("redis里面有，直接返回结果，不再打扰MySQL：「{}」", result);
                return result;
            }

            //8、redis中没有,去找mysql查询或者进行后续的业务逻辑
            //-----aop精华部分，才去找findUserById方法干活
            result = joinPoint.proceed();//主业务逻辑查询MySQl

            //9、MySQL步骤结束，还需要把结果存入redis一次，缓存补偿
            if (result != null) {
                log.info("redis 里面无，还需要把结果缓存入redis一次，缓存补偿：「{}」", result);
                redisTemplate.opsForValue().set(key, result);
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return result;

    }
}
